/**
* @file
* This file implements a basic server which handles requests from the client and handles them
* returning errors with errno where necessary or returning key values where required.
 * 
 * 
*/
#ifndef SERVER_H
#define SERVER_H


#include <stdio.h> // for File
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include "utils.h" // for logger()
#include "hash.h" // data structure for tables


static int listenfd; ///< There is only one storage server.


/**
* @brief This stores each connected client information on the server as a linked list.
*/
struct client
{
    int fd; ///< file descriptor of the client
    char username[MAX_USERNAME_LEN];
    char cryptpasswd [MAX_ENC_PASSWORD_LEN]; 
    bool auth; ///< client has been authenticated with the right password
    char buf[MAX_CMD_LEN]; ///< The buffer stored in this client's file descriptor
    int bytesleft; ///< How many data bytes in buf
    char *nextpos; ///< next position to read from buff, if non-NULL move this down to buf[0] before reading
    struct client *next; ///< pointer to the next client in the linked list
} *top = NULL; ///< the head of the linked list

/**
* @brief This function handles a command given by sending it as a string to a socket given.
* @param sock The socket descriptor to output commands to.
* @param cmd A string to be sent to a socket descriptor.
* @config_params
* @return 0 if success, returns error code if failure.
*/
int handle_command(int sock, char *cmd, struct config_params *params);

// TODO: Add to server.c later and just leave header definition here

/// This function adds a client with the given file descriptor.
static void addclient(int fd)
{
    struct client *p = malloc(sizeof(struct client));
    if(!p)
    {
	fprintf(stderr, "out of memory!\n");
	exit(1);
    }
    p->fd = fd;
    p->bytesleft = MAX_CMD_LEN;
    p->nextpos = NULL;
    p->auth = false; // not authenticated yet
    p->next = top;
    top = p; // p is now the first client in the list
}


/// This function removes a client.
static void removeclient(struct client *p)
{
    struct client **pp, *t;
    for (pp = &top; *pp && *pp != p; pp = &(*pp)->next)
	; // do nothing
    // Here, either we are at end of list or at pp.
    if (*pp) // If we are at pp
    {
	close((*pp)->fd); // close the file descriptor
	t = (*pp)->next; // t points to pp->next
	free(*pp); // free pp
	*pp = t; // let pointer pointing to pp point to next
    }
    else
    {
	// Error
	fprintf(stderr, "Trying to remove fd %d, it isn't in the list\n", p->fd);
	fflush(stderr);
    }
}

/// This function returns a client based on the fd given.
struct client *getclient(int fd)
{
    struct client **p = &top;
    while(*p)
    {
	if (((*p)->fd == fd))
	    return (*p);
	p =&(*p)->next;
    }
    return NULL;// client not found
}

/// This function prints client file descriptor.
void printclient()
{
    struct client **p = &top;
    while(*p)
    {
        printf("Client fd:%d\n", &(*p)->fd);
        p =&(*p)->next;
    }
    return; // finished printing

}

/// This function clears the client's buffer, then removes useless messages on a client's fd if it exists,and removes the client if it does not.
void cleanup(struct client *p)
{
    //============================
    // Clear the buffer (p->buf)
    //============================
    // clear the previous bytes
    if(p->nextpos) // if not NULL
	memmove(p->buf, p->nextpos, p->bytesleft); // copies bytes left from nextpost to beginning of buf
    // reset values
    p->nextpos = NULL;
    p->bytesleft = MAX_CMD_LEN;
    return;
}

/// This function reads the current client to the end of the linked list.
static void requeue(struct client *p)
{
    struct client *prev, *after, *final;
    for (final = top; final->next; final = final->next)
	; // let final point to the final node
    prev = top;
    if(prev == p) // if the client is the first node
    { // in this case, prev is p
	top = prev->next; // update top to point to 2nd node
			  // to make 2nd node first node
	final->next = prev; //make final next point to first node
	prev->next = NULL; // make first node next point to null
	return;
    }
    // here, client is not the first node
    for (prev = top; prev && prev->next != p; prev = prev->next)
        ; // Try and let pp->next point to p
    // Here, either we are at end of list or at p using pp->next
    if (prev) // If we are at prev
    {
        after = prev->next->next; // after points to p->next
	final->next = prev->next; // make the last node point to p
        (prev)->next = after; // make pointer before p point to p->next
	final->next->next = NULL; // make p->next point to NULL
	return;
    }
    else
    {
        // Error
        fprintf(stderr, "Trying to remove fd %d, it isn't in the list\n", p->fd);
        fflush(stderr);
    }
}

/// This function accepts new connections and updates the linked list.
void newconnection()
{
    int newfd;
    struct sockaddr_in r;
    socklen_t len = sizeof(r);
    if((newfd = Accept(listenfd, (struct sockaddr *)&r, &len)) < 0); // error if -1,
    addclient(newfd); // add the new client into the linked list
    // will include name & broadcast in read_process()
   return;
}

#endif
